package com;

import com.client.ExitGroupRespHandler;
import com.client.LoginRespHandler;
import com.client.ReceiveChatContentHandler;
import com.client.ReceiveGroupChatHandler;
import com.message.ChatLoginOutMessage;
import com.message.LoginReqMessage;
import com.message.Message;
import com.message.PingMessage;
import com.message.factory.InputTextToMessageToFactory;
import com.message.factory.MessageDeSerializerFactory;
import com.message.resp.ChatGroupCreateResp;
import com.protocol.SharedMessageCodec;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.handler.timeout.IdleStateHandler;
import lombok.extern.slf4j.Slf4j;

import java.util.Scanner;
import java.util.concurrent.CountDownLatch;

@Slf4j
public class ChatClient {

    private static volatile Channel socketChannel;

    private static final CountDownLatch CONNECT_SUCCESS_COUNT_DOWN_LATCH = new CountDownLatch(1);

    public static void main(String[] args) {

        NioEventLoopGroup nioEventLoopGroup = new NioEventLoopGroup();


        SharedMessageCodec SHARED_MESSAGE_CODEC = new SharedMessageCodec(new MessageDeSerializerFactory());

        Thread guiThread = new Thread(() -> {

            Scanner scanner = new Scanner(System.in);

            try {
                CONNECT_SUCCESS_COUNT_DOWN_LATCH.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println("请输入账号：");

            String userName = scanner.nextLine();

            System.out.println("请输入密码：");

            String passwd = scanner.nextLine();

            LoginReqMessage loginReqMessage = new LoginReqMessage(userName, passwd);

            socketChannel.writeAndFlush(loginReqMessage);

            while (true) {
                System.out.println("-----------------------------------");
                System.out.println("1. 发送消息 to 消息内容");
                System.out.println("2. 创建群聊 群聊名称 组内成员（成员之间使用逗号（,）分隔）");
                System.out.println("3. 发送群聊消息 群聊名称 消息内容");
                System.out.println("4. 退出群聊 群聊名称");
                System.out.println("5. 退出程序");
                String line = scanner.nextLine();
                String[] commandAndDataArray = line.trim().split(" ");

                Message message = InputTextToMessageToFactory.toMessage(commandAndDataArray);

                socketChannel.writeAndFlush(message);

                if (message instanceof ChatLoginOutMessage) {
                    nioEventLoopGroup.shutdownGracefully();
                    break;
                }
            }

        }, "用户交互窗口");


        guiThread.start();

        ReceiveChatContentHandler RECEIVE_CHAT_CONTENT_HANDLER = new ReceiveChatContentHandler();

        LoginRespHandler LOGIN_RESP_HANDLER = new LoginRespHandler(guiThread);

        ExitGroupRespHandler EXIT_GROUP_RESP_HANDLER = new ExitGroupRespHandler();

//        心跳包的发送时间间隔：大约为服务器检测客户端是否存活时间的一半
        int PING_TIME_OUT = 3;

        new Bootstrap()
//                SO_TIMEOUT 主要用于传统的阻塞IO
                // 指定时间之后仍无法连接，抛出 timeOutException
                .option(ChannelOption.CONNECT_TIMEOUT_MILLIS,300) //客户端只能给 SocketChannel 进行配置
                .group(nioEventLoopGroup)
                .channel(NioSocketChannel.class)
                .handler(new ChannelInitializer<NioSocketChannel>() {
                    @Override
                    protected void initChannel(NioSocketChannel ch) throws Exception {


                        ch.pipeline().addLast(
                                new IdleStateHandler(0, PING_TIME_OUT, 0),
                                new ChannelDuplexHandler(){
                                    @Override
                                    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {

                                        if(evt instanceof IdleStateEvent){
                                            IdleStateEvent stateEvent = (IdleStateEvent) evt;
                                            if(stateEvent.state()== IdleState.WRITER_IDLE){
                                                log.debug("{}秒计时已到，现在发送心跳包",PING_TIME_OUT);
                                                ctx.channel().writeAndFlush(new PingMessage());
                                            }
                                        }

                                        super.userEventTriggered(ctx, evt);
                                    }
                                },
//                                LOGGING_HANDLER,
                                new LengthFieldBasedFrameDecoder(1024, 11, 4),
                                SHARED_MESSAGE_CODEC,
                                new ActiveGuiThreadHandler(ch),
                                LOGIN_RESP_HANDLER,
                                RECEIVE_CHAT_CONTENT_HANDLER,
                                new SimpleChannelInboundHandler<ChatGroupCreateResp>() {
                                    @Override
                                    protected void channelRead0(ChannelHandlerContext ctx, ChatGroupCreateResp msg) throws Exception {
                                        System.out.printf("%s邀您加入群聊【%s】，当前群内组员有%s", msg.getChatGroupMasterName(), msg.getChatGroupName(), msg.getGroupMembers());
                                    }
                                },
                                new ReceiveGroupChatHandler(),
                                EXIT_GROUP_RESP_HANDLER

                        );
                    }
                }).connect("localhost", 8953);
    }

    private static class ActiveGuiThreadHandler extends SimpleChannelInboundHandler<Message> {
        private final NioSocketChannel ch;

        public ActiveGuiThreadHandler(NioSocketChannel ch) {
            this.ch = ch;
        }

        @Override
        protected void channelRead0(ChannelHandlerContext ctx, Message msg) throws Exception {
            log.info("收到消息：{}", msg);
            ctx.fireChannelRead(msg);
        }

        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {

            log.debug("连接服务成功！");

            socketChannel = ch;

            CONNECT_SUCCESS_COUNT_DOWN_LATCH.countDown();

            super.channelActive(ctx);
        }
    }
}
